<?php

namespace Zeke\Net\Http\Stream;

use Zeke\Zee\Util;
use Zeke\Zee\Event\EventEmitter;
use Zeke\Contracts\Io\Stream\ReadableStreamContracts;
use Zeke\Contracts\Io\Stream\WritableStreamContracts;



/**
 * [Internal] Limits the amount of data the given stream can emit
 *
 * This is used internally to limit the size of the underlying connection stream
 * to the size defined by the "Content-Length" header of the incoming request.
 *
 *
 * 获取指定长度的数据报文stream
 *
 */
class LengthLimitedStream extends EventEmitter implements ReadableStreamContracts
{
    /**
     * @var ReadableStreamContracts
     */
    private $stream;

    /**
     * @var bool
     */
    private $closed = false;


    /**
     * 已处理数据长度
     * @var int $transferredLength
     */
    private $transferredLength = 0;


    /**
     * @var int $maxLength 最大长度
     */
    private $maxLength;


    /**
     * 构造器
     * LengthLimitedStream constructor.
     * @param ReadableStreamContracts $stream
     * @param $maxLength
     */
    public function __construct(ReadableStreamContracts $stream, $maxLength)
    {
        $this->stream = $stream;
        $this->maxLength = $maxLength;

        //为子stream添加data事件处理函数，由子stream触发该事件
        $this->stream->on('data', array($this, 'handleData'));
        $this->stream->on('end', array($this, 'handleEnd'));
        $this->stream->on('error', array($this, 'handleError'));
        $this->stream->on('close', array($this, 'close'));
    }


    /**
     * stream可读
     *
     * @return bool
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:02
     */
    public function isReadable()
    {
        return !$this->closed && $this->stream->isReadable();
    }


    /**
     * 暂停
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:02
     */
    public function pause()
    {
        $this->stream->pause();
    }


    /**
     * 恢复
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:02
     */
    public function resume()
    {
        $this->stream->resume();
    }


    /**
     * 输入转输出
     *
     * @param WritableStreamContracts $dest
     * @param array $options
     * @return WritableStreamContracts
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:02
     */
    public function pipe(WritableStreamContracts $dest, array $options = array())
    {
        Util::pipe($this, $dest, $options);

        return $dest;
    }


    /**
     * 关闭连接
     *
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:03
     */
    public function close()
    {
        if ($this->closed) {
            return;
        }

        $this->closed = true;

        $this->stream->close();

        $this->emit('close');
        $this->removeAllListeners();
    }


    /**
     *
     * 消息处理回调函数,如果http报文超过最大长度，则报错
     *
     * @param string $data
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:03
     */
    public function handleData($data)
    {

        // transL = 120  dataL = 30  maxL = 200
        // transL = 180  dataL = 30  maxL = 200

        // 1: 如果 transL ＋ dataL >  maxL;将data切割，保留到maxL长度的data,剩余的数据舍弃;
        // 2: 累加transL ＝ transL ＋ dataL(被切割后的data);触发data事件;
        // 3: 如果 transL == maxL 触发end事件（http接收数据达到最大值），关闭数据流，移除data事件监听
        // 4: 如果 transL !== maxL;结束（本质上继续监听子级stream的data事件）


        // 第1步（$data按最大长度接收）
        if (($this->transferredLength + strlen($data)) > $this->maxLength) {
            // Only emit data until the value of 'Content-Length' is reached, the rest will be ignored
            //$this->maxLength - $this->transferredLength ＝ body还允许接收的长度
            $data = (string)substr($data, 0, $this->maxLength - $this->transferredLength);
        }

        // 第2步,只要有数据，即触发 data 事件
        if ($data !== '') {
            //执行data回调函数
            $this->transferredLength += strlen($data);
            $this->emit('data', array($data));
        }

        //第3步
        //接收的body长度 === http定义的最大值，需要报错处理
        if ($this->transferredLength === $this->maxLength) {
            // 'Content-Length' reached, stream will end
            $this->emit('end');
            $this->close();
            $this->stream->removeListener('data', array($this, 'handleData'));
        }
    }


    /**
     * 错误处理函数
     *
     * @param \Exception $e
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:04
     */
    public function handleError(\Exception $e)
    {
        $this->emit('error', array($e));
        $this->close();
    }


    /**
     * end事件处理函数
     *
     *
     * @author <liu.bin@ruaby.com> 2018-08-24 18:04
     */
    public function handleEnd()
    {
        if (!$this->closed) {
            //意外结束
            $this->handleError(new \Exception('Unexpected end event'));
        }
    }

}
